package priv.example.check;

import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * /TODO
 *
 * @author wjh
 **/
public class PreAuthorizeCheck extends AbstractCheck {

    private boolean mark;
    private String val;

    public void setMark(boolean mark) {
        this.mark = mark;
    }

    @Override
    public int[] getDefaultTokens() {
        return new int[]{TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF};
    }

    public int[] getAcceptableTokens() {
        return new int[0];
    }

    public int[] getRequiredTokens() {
        return new int[0];
    }

    @Override
    public void visitToken(DetailAST ast) {
//        log(ast.getLineNo(), "Method must exist PreAuthorize Annotation");
        DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
        if (modifiers.getChildCount() == 0){
            return;
        }
        Optional<DetailAST> requestMapping = getAnnotation(modifiers, REQUEST_MAPPING);
        String preVal = "";
        if (requestMapping.isPresent()){
            String requestMappingValue = getExprValue(requestMapping.get());
            if (!requestMappingValue.equals("")){
                preVal = handleMappingValue(requestMappingValue) + "/";
            }
        }else {
            if (!getAnnotation(modifiers, CONTROLLER).isPresent() && !getAnnotation(modifiers, REST_CONTROLLER).isPresent()){
                return;
            }
        }

        DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
        List<DetailAST> methods = getMethods(objBlock);
        for (DetailAST method : methods){
            val = null;
            if (!checkPreAuthorize(method, preVal)){
                String message = "Method must exist PreAuthorize Annotation : " + val;
                log(method.getLineNo(), message);
//                System.out.println(message);
            }
        }
    }


    private static final String REQUEST_MAPPING = "RequestMapping";
    private static final String CONTROLLER = "Controller";
    private static final String REST_CONTROLLER = "RestController";
    private static final String GET_MAPPING = "GetMapping";
    private static final String POST_MAPPING = "PostMapping";
    private static final String PRE_AUTHORIZE = "PreAuthorize";
    private static final String VALUE = "value";
    private static final String RETURN_TYPE = "Map";


    private boolean checkPreAuthorize(DetailAST ast, String pre){
        if (ast.findFirstToken(TokenTypes.TYPE).getChildCount(TokenTypes.IDENT) == 0){
            return true;
        }
        String type = ast.findFirstToken(TokenTypes.TYPE).findFirstToken(TokenTypes.IDENT).getText();
        if (!RETURN_TYPE.equals(type)){
            return true;
        }
        DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
        if (modifiers.getChildCount() == 0){
            return true;
        }
        String mappingVal = getMappingValue(modifiers);
        if (mappingVal == null){
            return true;
        }
        Optional<DetailAST> preAuthorize = getAnnotation(modifiers, PRE_AUTHORIZE);
        if (!preAuthorize.isPresent()){
            return false;
        }
        String preAuthorizeVal = getExprValue(preAuthorize.get());
        mappingVal = handleMappingValue(mappingVal);

        String[] tmp = preAuthorizeVal.split("#");
        tmp = tmp[1].split("'");
        preAuthorizeVal = tmp[0];
        String curVal = pre + mappingVal;

        boolean res =curVal.equals(preAuthorizeVal);
        if (!res){
            this.val = "expect: " + curVal + "  |  actual: " + preAuthorizeVal;
        }
        return curVal.equals(preAuthorizeVal);
    }


    private static Optional<DetailAST> getAnnotation(DetailAST ast, String text){
        return findByTypeAndText(ast, TokenTypes.ANNOTATION, text);
    }

    private static List<DetailAST> getMethods(DetailAST ast){
        List<DetailAST> detailASTS = new ArrayList<>();
        for (DetailAST cur = ast.getFirstChild(); cur != null; cur = cur.getNextSibling()) {
            if (cur.getType() == TokenTypes.METHOD_DEF) {
                detailASTS.add(cur);
            }
        }
        return detailASTS;
    }

    private static String getExprValue(DetailAST ast){
        return ast.findFirstToken(TokenTypes.EXPR)
                .findFirstToken(TokenTypes.STRING_LITERAL).getText();
    }

    private static Optional<DetailAST> getAnnotationMemberValuePair(DetailAST ast, String text){
        return findByTypeAndText(ast, TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR, text);
    }

    private static Optional<DetailAST> findByTypeAndText(DetailAST ast, int type, String text){
        for (DetailAST cur = ast.getFirstChild(); cur != null; cur = cur.getNextSibling()) {
            if (cur.getType() == type && text.equals(cur.findFirstToken(TokenTypes.IDENT).getText())) {
                return Optional.of(cur);
            }
        }
        return Optional.empty();
    }

    private static String getMappingValue(DetailAST ast){
        for (DetailAST cur = ast.getFirstChild(); cur != null; cur = cur.getNextSibling()) {
            if (cur.getType() == TokenTypes.ANNOTATION) {
                switch (cur.findFirstToken(TokenTypes.IDENT).getText()){
                    case REQUEST_MAPPING:
                    {
                        if (cur.getChildCount(TokenTypes.EXPR) > 0){
                            return getExprValue(cur);
                        }
                        Optional<DetailAST> annotationMemberValuePair = getAnnotationMemberValuePair(cur, VALUE);
                        if (!annotationMemberValuePair.isPresent()){
                            return "";
                        }
                        return getExprValue(annotationMemberValuePair.get());
                    }
                    case GET_MAPPING:
                    case POST_MAPPING:{
                        return getExprValue(cur);
                    }
                }
            }
        }
        return null;
    }

    private static String handleMappingValue(String val){
        val = val.substring(1, val.length() - 1);
        if (val.startsWith("/")) {
            val = val.substring(1);
        }
        return val;
    }

}
